using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SocketPorxyServer;
using SuperSocket.Command;

namespace SuperSocketProxy.Commands;

[Command(Key = (int)Command.HttpHandshake)]
public sealed class HttpHandshake : SocketProxyAsyncCommand<HttpHandshakePacket>
{
    private readonly ProxyOptions _proxyOptions;
    
    public HttpHandshake(IOptions<ProxyOptions> options)
    {
        _proxyOptions = options.Value;
    }
    
    protected override async ValueTask ExecuteAsync(SocketProxySession session, HttpHandshakePacket package)
    {
        if (!_proxyOptions.EnableHttpProxy)
        {
            session.LogError($"远程地址：{session.RemoteEndPoint} proxy：{session.LocalEndPoint} 未开启http proxy");
            throw new CloseSessionException();
        }
        
        session.ProxyType = package.ProxyType;
        
        session.Inilizetion();

        var response = session.PacketResponse;
        
        if (!session.VerifyUser(package.Username, package.Password))
        {
            session.LogError($"远程地址：{session.RemoteEndPoint} proxy：{session.LocalEndPoint} 密码错误");
            await response.SendVerifyAsync(verifyResult: false);
            throw new CloseSessionException();
        }
        
        using var connectTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(_proxyOptions.ConnectTargetTimeout)) ;
        using var source = CancellationTokenSource.CreateLinkedTokenSource(connectTokenSource.Token, session.CancellationToken.Token);

        var unException = false;
        
        try
        {
            session.IsConnected = await session.ConnectTargetAsync(package.RemoteEndPoint, source.Token);
            unException = true;
        }
        catch (TimeoutException)
        {
            session.LogError($"代理地址：{session.LocalEndPoint} 连接到：{package.RemoteEndPoint} 超时");
            await response.SendConnectAsync(ConnectTargetErrorType.TtlExpired);
            return;
        }
        catch (NotSupportedAddressException)
        {
            session.LogError($"代理地址：{session.LocalEndPoint} 连接：{package.RemoteEndPoint} 地址失败，不支持此地址");
            await response.SendConnectAsync(ConnectTargetErrorType.NotSupportedAddress);
            return;
        }
        catch (Exception e)
        {
            session.LogError(e, $"代理地址：{session.LocalEndPoint} 连接到：{package.RemoteEndPoint} 出现未知错误");
            await response.SendConnectAsync(ConnectTargetErrorType.UnError);
            return;
        }
        finally
        {
            if (!unException)//如果抛出异常则抛出关闭session异常
                throw new CloseSessionException();
        }
        
        if (!session.IsConnected)
        {
            session.LogError($"代理地址：{session.LocalEndPoint} 连接到：{package.RemoteEndPoint} 失败");
            await response.SendConnectAsync(ConnectTargetErrorType.ConnectFail);
            throw new CloseSessionException();
        }

        session.Port = package.Port;
        session.TargetRemoteEndPoint = package.RemoteEndPoint;
        session.LogInformation($"代理地址：{session.LocalEndPoint} 连接到：{package.RemoteEndPoint} 成功");
        await response.SendConnectHttpAsync();
    }
}